﻿using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace JackProjectTemplate.Cache.DistributedIdentifyGeneraters.SequentialGuid
{
    public static class SequentialGuidExtensions
    {
#if NETFRAMEWORK || NETSTANDARD2_0
    // Was added in .NET Standard 2.1 and later so we only need to provide it for .NET Framework
    private static readonly DateTime UnixEpoch =
        new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
#endif
        //See: https://www.sqlbi.com/blog/alberto/2007/08/31/how-are-guids-sorted-by-sql-server/
        //See: https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/comparing-guid-and-uniqueidentifier-values
        private static readonly int[] _guidIndex = [13, 12, 11, 10, 15, 14, 9, 8, 6, 7, 4, 5, 0, 1, 2, 3];
        private static readonly int[] _sqlGuidIndex = [12, 13, 14, 15, 10, 11, 8, 9, 7, 6, 3, 2, 1, 0, 5, 4];

        private static DateTime ToDateTime(this long ticks) =>
            new DateTime(ticks, DateTimeKind.Utc);

        public static DateTime? ToDateTime(this Guid guid)
        {
            var ticks = guid.ToTicks();
            if (ticks.IsDateTime()) return ticks.ToDateTime();

            //Try conversion through sql guid
            ticks = new SqlGuid(guid).ToGuid().ToTicks();
            return ticks.IsDateTime()
                ? ticks.ToDateTime()
                : default(DateTime?);
        }

        public static DateTime? ToDateTime(this SqlGuid sqlGuid) =>
        sqlGuid.ToGuid().ToDateTime();

        public static Guid ToGuid(this SqlGuid sqlGuid)
        {
            var bytes = sqlGuid.ToByteArray()
#if NET6_0_OR_GREATER
                !
#endif
            ;
            return new Guid(_guidIndex.Select(i => bytes[i]).ToArray());
        }

        public static SqlGuid ToSqlGuid(this Guid guid)
        {
            var bytes = guid.ToByteArray();
            return new SqlGuid(_sqlGuidIndex.Select(i => bytes[i]).ToArray());
        }

        internal static bool IsDateTime(this long ticks) =>
            ticks <= DateTime.UtcNow.Ticks &&
            ticks >=
#if NETFRAMEWORK || NETSTANDARD2_0
                    UnixEpoch
#else
                    DateTime.UnixEpoch
#endif
                            .Ticks;

        private static long ToTicks(this Guid guid)
        {
            var bytes = guid.ToByteArray();
            return
                ((long)bytes[3] << 56) +
                ((long)bytes[2] << 48) +
                ((long)bytes[1] << 40) +
                ((long)bytes[0] << 32) +
                ((long)bytes[5] << 24) +
                (bytes[4] << 16) +
                (bytes[7] << 8) +
                bytes[6];
        }
    }
}
